﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Core.Tools
{
    public static class NumberHelper
    {
        public const int MaxRadix = 64;
        public const int MinRadix = 2;

        public static readonly char[] Digitals;
        static NumberHelper()
        {
            Digitals = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c',
                'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
                'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
                'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '~', '!' };
        }

        /// <summary>
        /// 判断一个字符串是否为数字格式
        /// </summary>
        /// <param name="LpText">字符串</param>
        /// <param name="Begin">字符串开始索引</param>
        /// <param name="End">字符串结束索引，不包含此索引</param>
        /// <param name="Radix">字符进制</param>
        /// <param name="IsNegative">返回是否为负数</param>
        /// <param name="IsFloat">返回是否为浮点数</param>
        /// <param name="Digit">返回整数位数</param>
        /// <param name="Float">返回小数位数</param>
        /// <returns>返回此字符串是否为数字格式</returns>
        public unsafe static bool IsNumber(char* LpText, int Begin, int End, int Radix, out bool IsNegative, out bool IsFloat, out int Digit, out int Float)
        {
            if (Radix < 2 || Radix > 62)
            {
                throw new ArgumentException("Radix");
            }

            Float = 0;
            Digit = 0;

            IsFloat = false;
            IsNegative = false;

            if (Begin >= End)
            {
                return false;
            }

            --Radix;


            if (LpText[Begin] == '-')
            {
                IsNegative = true;
            }
            else if (LpText[Begin] >= Digitals[0] && LpText[Begin] <= Digitals[Radix])
            {
                Digit = 1;
            }
            else if (LpText[Begin] != '+')
            {
                return false;
            }

            ++Begin;

            while (Begin < End)
            {
                if (LpText[Begin] >= Digitals[0] && LpText[Begin] <= Digitals[Radix])
                {
                    ++Digit;

                    ++Begin;
                }
                else if (LpText[Begin] == '.' && Digit != 0)
                {
                    IsFloat = true;

                    ++Begin;

                    while (Begin < End)
                    {
                        if (LpText[Begin] >= Digitals[0] && LpText[Begin] <= Digitals[Radix])
                        {
                            ++Float;

                            ++Begin;
                        }
                        else
                        {
                            return false;
                        }
                    }

                    return Float != 0;
                }
                else
                {
                    return false;
                }
            }

            return Digit != 0;
        }

        /// <summary>
        /// 判断一个字符串是否为数字格式
        /// </summary>
        /// <param name="Text">字符串</param>
        /// <param name="Radix">字符进制</param>
        /// <param name="IsNegative">返回是否为负数</param>
        /// <param name="IsFloat">返回是否为浮点数</param>
        /// <param name="Digit">返回整数位数</param>
        /// <param name="Float">返回小数位数</param>
        /// <returns>返回此字符串是否为数字格式</returns>
        public unsafe static bool IsNumber(string Text, int Radix, out bool IsNegative, out bool IsFloat, out int Digit, out int Float)
        {
            if (Text == null)
            {
                throw new NullReferenceException("Text");
            }

            fixed (char* LpText = Text)
            {
                return IsNumber(LpText, 0, Text.Length, Radix, out IsNegative, out IsFloat, out Digit, out Float);
            }
        }

        public static int GetLength(long Number, int Radix)
        {
            int Result = Number < 0 ? 1 : 0;

            do
            {
                ++Result;

                Number /= Radix;

            } while (Number != 0);

            return Result;
        }

        public static int ParseDigital(char Char, int Radix)
        {
            char Max = Digitals[Radix - 1];

            if (Radix <= 10)
            {
                if (Char >= '0' && Char <= Max)
                {
                    return Char - 48;
                }
            }
            else if (Radix <= 36)
            {
                if (Char >= '0' && Char <= '9')
                {
                    return Char - 48;
                }

                if (Char >= 'a' && Char <= Max)
                {
                    return Char - 87;
                }

                Char += (char)32;

                if (Char >= 'a' && Char <= Max)
                {
                    return Char - 87;
                }
            }
            else if (Radix <= 62)
            {
                if (Char >= '0' && Char <= '9')
                {
                    return Char - 48;
                }

                if (Char >= 'a' && Char <= 'z')
                {
                    return Char - 87;
                }

                if (Char >= 'A' && Char <= Max)
                {
                    return Char - 29;
                }
            }
            else if (Radix <= 64)
            {
                if (Char >= '0' && Char <= '9')
                {
                    return Char - 48;
                }

                if (Char >= 'a' && Char <= 'z')
                {
                    return Char - 87;
                }

                if (Char >= 'A' && Char <= Max)
                {
                    return Char - 29;
                }

                if (Char == '~')
                {
                    return 62;
                }

                if (Radix == 64 && Char == '!')
                {
                    return 63;
                }
            }

            return -1;
        }

        public unsafe static bool TryParseNumber<T>(char* LpText, int Begin, int End, int Radix, out T Result)
        {
            Result = default(T);

            if (Radix <= 1 || Radix > 64)
            {
                return false;
            }

            if (Begin >= End)
            {
                return false;
            }

            bool IsNegative = false;

            if (LpText[Begin] == '-')
            {
                IsNegative = true;

                ++Begin;
            }
            else if (LpText[Begin] == '+')
            {
                ++Begin;
            }

            var TypeCode = Type.GetTypeCode(typeof(T));

            switch (TypeCode)
            {
                case TypeCode.Char:
                case TypeCode.SByte:
                case TypeCode.Byte:
                case TypeCode.Int16:
                case TypeCode.UInt16:
                case TypeCode.Int32:
                case TypeCode.UInt32:
                case TypeCode.Int64:
                case TypeCode.UInt64:
                    goto Integer;
                case TypeCode.Single:
                case TypeCode.Double:
                    goto Float;
                case TypeCode.Decimal:
                    goto Decimal;
                default:
                    goto Dynamic;
            }

            #region Integer

            Integer:

            long Integer = 0;

            int Digital;

            while (Begin < End)
            {
                Digital = ParseDigital(LpText[Begin], Radix);

                if (Digital == -1)
                {
                    return false;
                }

                Integer = Integer * Radix + Digital;

                ++Begin;
            }

            if (IsNegative)
            {
                Integer = -Integer;
            }

            switch (TypeCode)
            {
                case TypeCode.Char:
                    Result = (T)(object)(char)Integer;
                    return true;
                case TypeCode.SByte:
                    Result = (T)(object)(sbyte)Integer;
                    return true;
                case TypeCode.Byte:
                    Result = (T)(object)(byte)Integer;
                    return true;
                case TypeCode.Int16:
                    Result = (T)(object)(short)Integer;
                    return true;
                case TypeCode.UInt16:
                    Result = (T)(object)(ushort)Integer;
                    return true;
                case TypeCode.Int32:
                    Result = (T)(object)(int)Integer;
                    return true;
                case TypeCode.UInt32:
                    Result = (T)(object)(uint)Integer;
                    return true;
                case TypeCode.Int64:
                    Result = (T)(object)Integer;
                    return true;
                case TypeCode.UInt64:
                    Result = (T)(object)(ulong)Integer;
                    return true;
            }

            #endregion

            #region Float

            Float:

            double Float = 0;

            while (Begin < End)
            {
                var Char = LpText[Begin];

                ++Begin;

                if (Char == '.')
                {
                    break;
                }

                Digital = ParseDigital(Char, Radix);

                if (Digital == -1)
                {
                    return false;
                }

                Float = Float * Radix + Digital;
            }

            long Flag = Radix;

            while (Begin < End)
            {
                Digital = ParseDigital(LpText[Begin], Radix);

                if (Digital == -1)
                {
                    return false;
                }

                Float += (double)Digital / Flag;

                Flag *= Radix;

                ++Begin;
            }

            if (IsNegative)
            {
                Float = -Float;
            }

            switch (TypeCode)
            {
                case TypeCode.Single:
                    Result = (T)(object)(float)Float;
                    return true;
                case TypeCode.Double:
                    Result = (T)(object)Float;
                    return true;
            }

            #endregion

            #region Decimal

            Decimal:

            Flag = 0;

            Decimal Decimal = 0;

            while (Begin < End)
            {
                var Char = LpText[Begin];

                ++Begin;

                if (Char == '.')
                {
                    if (Flag != 0)
                    {
                        return false;
                    }

                    Flag = 1;

                    continue;
                }

                if (Flag != 0)
                {
                    Flag *= Radix;
                }

                Digital = ParseDigital(Char, Radix);

                if (Digital == -1)
                {
                    return false;
                }

                Decimal = Decimal * Radix + Digital;
            }

            if (Flag != 0)
            {
                Decimal /= Flag;
            }

            if (IsNegative)
            {
                Decimal = -Decimal;
            }

            Result = (T)(object)Decimal;

            return true;

            #endregion

            #region Dynamic

            Dynamic:

            Flag = 0;

            dynamic Dynamic = default(T);

            try
            {
                while (Begin < End)
                {
                    var Char = LpText[Begin];

                    ++Begin;

                    if (Char == '.')
                    {
                        if (Flag != 0)
                        {
                            return false;
                        }

                        Flag = 1;

                        continue;
                    }

                    if (Flag != 0)
                    {
                        Flag *= Radix;
                    }

                    Digital = ParseDigital(Char, Radix);

                    if (Digital == -1)
                    {
                        return false;
                    }

                    Dynamic = Dynamic * Radix + Digital;
                }

                if (Flag != 0)
                {
                    Dynamic /= Flag;
                }

                if (IsNegative)
                {
                    Dynamic = -Dynamic;
                }

                Result = Dynamic;

                return true;
            }
            catch (Exception)
            {
                return false;
            }

            #endregion
        }

        public unsafe static bool TryParseNumber<T>(string Text, int Radix, out T Result)
        {
            fixed (char* LpText = Text)
            {
                return TryParseNumber(LpText, 0, Text.Length, Radix, out Result);
            }
        }

        internal unsafe static int ToString(long Number, int Radix, char* LpText, int Begin)
        {
            if (Number < 0)
            {
                Number = -Number;

                LpText[Begin] = '-';

                ++Begin;
            }
            else if (Number == 0)
            {
                LpText[Begin] = '0';

                return Begin + 1;
            }

            long Temp = Number;

            while (Temp != 0)
            {
                ++Begin;

                Temp /= Radix;
            }

            Temp = Begin;

            while (Number != 0)
            {
                --Temp;

                LpText[Temp] = Digitals[Number % Radix];

                Number /= Radix;
            }

            return Begin;
        }
    }
}